package com.qire.antscompiler.generator;

import com.qire.antscompiler.utils.Log;
import com.qire.antscore.annotation.PropertyObserve;
import com.qire.antscore.annotation.PropertyObserve.BindType;
import com.qire.antscore.common.AssertUtils;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;

/**
 * ViewModel属性观察者绑定器代码生成器
 */
public class PropertyObserveCodeGenerator {

    // ViewModel类全名
    private static final String ANTS_VIEW_MODEL = "com.qire.antsbinder.viewModel.AntsViewModel";
    // todo 可以不用接口约束，改成静态方法调用，约定好方法名即可
    // 绑定器接口全名
    private static final String I_OBSERVE_BINDER = "com.qire.antsbinder.viewModel.IObserveBinder";
    // 绑定器接口方法名
    private static final String METHOD_OBSERVE_BIND = "observeBind";
    // 类名后缀
    private static final String CLASS_NAME_SUFFIX = "PropertyObserveBinder";

    private Log log;

    // 节点工具类 (类、函数、属性都是节点)
    private Elements elementUtils;

    // 文件生成器 类/资源
    private Filer filerUtils;

    /**
     * 记录所有需要注入的属性 key:类节点 value:需要注入的属性节点集合
     */
    private Map<TypeElement, List<Element>> parentAndChild = new HashMap<>();

    public PropertyObserveCodeGenerator(ProcessingEnvironment processingEnv){
        this.log = Log.newLog(processingEnv.getMessager());
        this.elementUtils = processingEnv.getElementUtils();
        this.filerUtils = processingEnv.getFiler();
    }

    /**
     * 记录需要生成的类与属性
     *
     * @param elements
     * @throws IllegalAccessException
     */
    private void categories(Set<? extends Element> elements) {
        for (Element element : elements) {
            //获得父节点 (类)
            TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
            if (parentAndChild.containsKey(enclosingElement)) {
                parentAndChild.get(enclosingElement).add(element);
            } else {
                List<Element> childList = new ArrayList<>();
                childList.add(element);
                parentAndChild.put(enclosingElement, childList);
            }
        }
    }

    /**
     * 解析观察者绑定器
     */
    public void parsePropertyObserve(Set<? extends Element> elements) {

        if(AssertUtils.isEmpty(elements)) {
            log.i("PropertyObserveCodeGenerator：        elements 为空没有需要处理的");
            return;
        }
        log.i("PropertyObserveCodeGenerator：        开始处理了！");

        // 更具归属类分类
        categories(elements);

        if(AssertUtils.isEmpty(parentAndChild)) {
            return;
        }

        // 获得ViewModel类型
        TypeElement AntsViewModel = elementUtils.getTypeElement(ANTS_VIEW_MODEL);
        // todo 可以不用接口约束，改成静态方法调用，约定好方法名即可
        // 获得观察者绑定器结构类型
        TypeElement IObserveBinder = elementUtils.getTypeElement(I_OBSERVE_BINDER);

        for (Map.Entry<TypeElement, List<Element>> entry : parentAndChild.entrySet()) {

            List<Element> observeActionElementList = entry.getValue();
            if(AssertUtils.isEmpty(observeActionElementList)) {
                continue;
            }

            // 获得观察者行为所属对象真是类型
            TypeElement observeClassElement = entry.getKey();
            // 转化为ClassName
            ClassName observeClassName = ClassName.get(observeClassElement);

            // 构建一个类型 AntsViewModel<?>
            ParameterizedTypeName AntsViewModelTypeName = ParameterizedTypeName.get(
                    ClassName.get(AntsViewModel),
                    TypeVariableName.get("?"));
            // 申明一个变量 AntsViewModel<?> viewModel
            ParameterSpec viewModelSpec = ParameterSpec.builder(AntsViewModelTypeName, "viewModel").build();

            // 申明一个变量 T observe
            ParameterSpec observeSpec = ParameterSpec.builder(observeClassName, "observe").build();

            // todo 可以不用接口约束，改成静态方法调用，约定好方法名即可
            // 定义一个方法
            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(METHOD_OBSERVE_BIND)
                    .addAnnotation(Override.class)
                    .addModifiers(Modifier.PUBLIC)
//                    .addModifiers(Modifier.STATIC)
                    .addParameter(viewModelSpec)
                    .addParameter(observeSpec);

            // 为方法 public void observeBind(AntsViewModel viewModel, T observe) {} 添加函数体
            for(Element observeActionElement : observeActionElementList) {
                // 验证节点是否含有属性绑定注解
                PropertyObserve propertyObserve = observeActionElement.getAnnotation(PropertyObserve.class);
                if(propertyObserve == null) {
                    continue;
                }
                if(propertyObserve.bindType() == BindType.ObserveForever) {
//                    viewModel.observeForever("test1",observe::setTest1);
                    methodBuilder.addStatement("viewModel.observeForever(\"$L\",observe::$L)",
                            propertyObserve.name(),
                            observeActionElement.getSimpleName());
                } else {
//                    viewModel.observe("test1",owner,observe::setTest1);
                }
            }

            // 生成java类名
            String newObserveClassName = observeClassElement.getSimpleName() + CLASS_NAME_SUFFIX;

            // 构建一个IObserveBinder<T>类型
            ParameterizedTypeName superinterfaceTypeName = ParameterizedTypeName.get(
                    ClassName.get(IObserveBinder),
                    observeClassName);

            // 构建一个 public class ****PropertyObserveBinder implements IObserveBinder{} 类
            TypeSpec PropertyObserveBinderClass = TypeSpec.classBuilder(newObserveClassName)
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(methodBuilder.build())
                    .addSuperinterface(superinterfaceTypeName)
                    .build();

            //将****PropertyObserveBinder文件写入磁盘中,路径是在app/build/source/api/debug/下面
            try {
                JavaFile.builder(observeClassName.packageName(), PropertyObserveBinderClass).build().writeTo(filerUtils);
            } catch (IOException e) {
                e.printStackTrace();
            }

            log.i("PropertyObserveCodeGenerator 构建成功: " + observeClassName.packageName() + "." + newObserveClassName);
        }
    }

}
